package redis.redisgen; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheException; import com.github.mustachejava.MustacheFactory; import com.google.common.base.Function; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Multimaps; import com.sampullara.cli.Args; import com.sampullara.cli.Argument; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.map.MappingJsonFactory; import org.w3c.dom.Document; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.*; import java.net.URL; import java.net.URLEncoder; import java.util.*; /** * Generate client code for redis based on the protocol. * <p/> * User: sam * Date: 11/5/11 * Time: 9:10 PM */ @SuppressWarnings({"UnusedDeclaration", "FieldCanBeLocal"}) public class Main { @Argument(alias = "l") private static String language = "java"; @Argument(alias = "p") private static String pkg = "redis.client"; @Argument(alias = "d", required = true) private static File dest; @Argument(alias = "t") private static String template = "client"; @Argument(alias = "n") private static String className = "RedisClient"; private static Set<String> keywords = new HashSet<String>() {{ add("type"); add("object"); }}; @SuppressWarnings("ResultOfMethodCallIgnored") public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, MustacheException { try { Args.parse(Main.class, args); } catch (IllegalArgumentException e) { Args.usage(Main.class); System.exit(1); } MustacheFactory mb = new DefaultMustacheFactory("templates/" + language) { @Override public void encode(String value, Writer writer) { try { writer.write(value); } catch (IOException e) { throw new MustacheException(); } } }; Mustache mustache = mb.compile(template + ".txt"); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); XPathFactory xpf = XPathFactory.newInstance(); final DocumentBuilder db = dbf.newDocumentBuilder(); final XPathExpression replyX = xpf.newXPath().compile("//a"); final Properties cache = new Properties(); File cacheFile = new File("cache"); if (cacheFile.exists()) { cache.load(new FileInputStream(cacheFile)); } Set<String> ungenerated = new HashSet<String>( Arrays.asList( "MULTI", "EXEC", "DISCARD", // Transactions "PSUBSCRIBE", "SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", // subscriptions "AUTH" // authentication ) ); final Set<String> genericReply = new HashSet<String>(Arrays.asList( "SORT", // Can return an integer reply "ZRANK", "ZREVRANK", // Two different return values "SRANDMEMBER", // Can return a bulk or multibulk reply depending on count "BRPOPLPUSH" // If the timeout occurs it returns a Nil multi-bulk reply instead of a bulk reply )); final Set<String> multiples = new HashSet<String>(Arrays.asList( "ZADD" )); JsonFactory jf = new MappingJsonFactory(); JsonParser jsonParser = jf.createJsonParser(new URL("https://raw.github.com/antirez/redis-doc/master/commands.json")); final JsonNode commandNodes = jsonParser.readValueAsTree(); Iterator<String> fieldNames = commandNodes.getFieldNames(); ImmutableListMultimap<String,String> group = Multimaps.index(fieldNames, new Function<String, String>() { @Override public String apply(String s) { return commandNodes.get(s).get("group").asText(); } }); List<Object> commands = new ArrayList<Object>(); for (Map.Entry<String, String> entry : group.entries()) { String key = entry.getKey(); final String groupName = key.substring(0, 1).toUpperCase() + key.substring(1); final String command = entry.getValue(); if (ungenerated.contains(command)) continue; final boolean splitCommand = command.contains(" "); final String safeCommand = command.replace(" ", "_"); String cacheReply = cache.getProperty(command.toLowerCase()); if (cacheReply == null) { final Document detail = db.parse("http://query.yahooapis.com/v1/public/yql/javarants/redisreply?url=" + URLEncoder.encode("http://redis.io/commands/" + safeCommand.toLowerCase(), "utf-8")); cacheReply = replyX.evaluate(detail).replaceAll("[- ]", "").replaceAll("reply", "Reply").replaceAll("bulk", "Bulk").replaceAll("Statuscode", "Status"); cache.setProperty(safeCommand.toLowerCase(), cacheReply); cache.store(new FileWriter(cacheFile), "# Updated " + new Date()); } final String finalReply = cacheReply; final JsonNode commandNode = commandNodes.get(command); commands.add(new Object() { String group = groupName; boolean split_command = splitCommand; String name = safeCommand; String name1 = splitCommand ? name.substring(0, name.indexOf("_")) : name; String name2 = splitCommand ? name.substring(name.indexOf("_") + 1) : ""; String comment = commandNode.get("summary").getTextValue(); boolean generic = finalReply.equals("") || genericReply.contains(name); String reply = generic ? "Reply" : finalReply; String version = commandNode.get("since").getTextValue(); boolean hasOptional = false; boolean hasMultiple = false; List<String> skipped = new ArrayList<String>(); boolean varargs() { return (hasMultiple || hasOptional); } boolean usearray = false; List<Object> arguments = new ArrayList<Object>(); int base_length() { return arguments.size() - (hasMultiple ? 1 : 0); } { JsonNode argumentArray = commandNode.get("arguments"); if (argumentArray != null) { if (argumentArray.size() > 3) { usearray = true; } boolean first = true; int argNum = 0; if (multiples.contains(name)) { arguments.add(new Object() { boolean first = true; boolean multiple = true; String typename = "Object"; String name = "args"; }); } else { for (final JsonNode argumentNode : argumentArray) { JsonNode nameNodes = argumentNode.get("name"); final String argName; if (nameNodes.isArray()) { boolean f = true; StringBuilder sb = new StringBuilder(); for (JsonNode nameNode : nameNodes) { if (!f) { sb.append("_or_"); } f = false; String textValue = nameNode.getTextValue(); sb.append(textValue); } argName = sb.toString(); } else { argName = nameNodes.getTextValue(); } final boolean finalFirst = first; final int finalArgNum = argNum; final boolean isMultiple = argumentNode.get("multiple") != null || argumentNode.get("command") != null; final boolean isOptional = argumentNode.get("optional") != null; if (isOptional) hasOptional = true; if (isMultiple) hasMultiple = true; final int finalArgNum1 = argNum; arguments.add(new Object() { int arg_num = finalArgNum1; boolean first = finalFirst; boolean multiple = isMultiple; String typename = "Object"; String name() { String name = (argName + finalArgNum).replaceAll("[- :]", "_"); for (String s : skipped) { name = s + "_" + name; } return name; } Boolean optional = isOptional; boolean skip() { boolean skip = hasMultiple && isOptional && !isMultiple; if (skip) { skipped.add(argName); } return skip; } }); if (isMultiple) { usearray = true; } first = false; argNum++; if (isMultiple) break; } } } } String methodname = safeCommand.toLowerCase(); String quote = keywords.contains(methodname) ? "`" : ""; }); } Map<String, Object> ctx = new HashMap<String, Object>(); ctx.put("commands", commands); File base = new File(dest, pkg.replace(".", "/")); base.mkdirs(); mustache.execute(new FileWriter(new File(base, className + "." + language)), ctx).close(); } }